/*
 * Copyright:  2018 smarabbit studio.
 *
 *  Licensed under the Confluent Community License; you may not use this file
 *  except in compliance with the License.  You may obtain a copy of the License at
 *
 *  http://www.confluent.io/confluent-community-license
 *
 *  Unless required by applicable law or agreed to in writing, software
 *  distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
 *  WARRANTIES OF ANY KIND, either express or implied.  See the License for the
 *  specific language governing permissions and limitations under the License.
 *
 *  @作   者： 黄开晖 (117227773@qq.com)
 *  @日   期:  2019年1月7日
 */
package com.massyframework.beanskin.guice.beancontext;

import java.io.IOException;
import java.util.Map;

import javax.servlet.Filter;
import javax.servlet.FilterChain;
import javax.servlet.FilterConfig;
import javax.servlet.ServletContext;
import javax.servlet.ServletException;
import javax.servlet.ServletRequest;
import javax.servlet.ServletResponse;

import com.google.inject.Injector;
import com.massyframework.beanskin.assembly.Assembly;
import com.massyframework.beanskin.assembly.beans.BeansException;
import com.massyframework.beanskin.assembly.handling.EnvironmentHandler;
import com.massyframework.beanskin.assembly.handling.ReadyingException;
import com.massyframework.beanskin.assembly.handling.ReadyingHandler;
import com.massyframework.beanskin.service.ServicePropertiesBuilder;
import com.massyframework.beanskin.service.ServiceRegistration;
import com.massyframework.beanskin.service.ServiceRegistry;
import com.massyframework.beanskin.service.ServiceRepository;
import com.massyframework.beanskin.web.utils.FilterRegistrationUtils;

/**
 * 支持支持Filter的Guice容器
 * @author huangkh
 *
 */
public final class GuiceFilterHandler extends GuiceHandler
        implements EnvironmentHandler, ReadyingHandler {

    private volatile boolean runAtJ2EE = true;
    private volatile ServiceRegistration<Filter> filterRegistration;
    private volatile FilterConfig config;
    private volatile Filter filter;

    public GuiceFilterHandler() {
    }

    /**
     * 初始化
     *
     * @throws Exception 发生非预期的例外
     */
    @Override
    protected void init() throws Exception {
        super.init();

        final Assembly assembly = this.getAssociatedAssembly();
        ServiceRepository repository = this.getServiceRepository(assembly);

        ServletContext context = repository.findService(ServletContext.class).orElse(null);
        this.runAtJ2EE = context != null;

        if (this.runAtJ2EE) {
            FilterRegistrationUtils.createFilterRegistration(
                    context,
                    assembly,
                    null,
                    "true");
        }
    }

    /**
     * 析构，释放资源
     */
    @Override
    public void destroy() {
        super.destroy();
    }

    /**
     * 执行就绪
     *
     * @throws ReadyingException 就绪时发生例外
     */
    @Override
    public void readying() throws ReadyingException {
        if (!this.runAtJ2EE) {
            return;
        }

        this.exportService();
    }

    /**
     * 退出就绪
     *
     * @throws ReadyingException 退出就绪时发生的例外
     */
    @Override
    public void unreadyed() throws ReadyingException {
        this.unexportService();
    }

    /**
     * 在Web场景下注册的组件是否已准备就绪？<br>
     * Servlet的实例化要视注册时的选项，如果是首次请求时才实例化，那在发起首次请求前，本方法只能返回<code>false</code>
     *
     * @return {@link boolean},返回<code>true</code>表示已就绪，否则返回<code>false</code>
     */
    @Override
    public boolean isReady() {
        if (!this.runAtJ2EE) {
            return false;
        }

        return this.config != null;
    }

    /**
     * 创建{@link GuiceBeanContextAdapter}
     *
     * @param injector {@link Injector}
     * @return {@link GuiceBeanContextAdapter}
     */
    @Override
    protected GuiceBeanContextAdapter createBeanContext(Injector injector) throws Exception {
        GuiceBeanContextAdapter result =  super.createBeanContext(injector);

        Map<String, Filter> filterMap = result.getBeansOfType(Filter.class);
        if (filterMap.size() == 0){
            throw new BeansException("cannot found Filter from guice.");
        }
        if (filterMap.size() > 1) {
            throw new BeansException("found more than one Filter from guice.");
        }

        Filter filter = filterMap.values().iterator().next();
        filter.init(this.config);
        this.filter = filter;
        return result;
    }

    /**
     * 取消装配件上下文注册
     */
    @Override
    protected void unregisterAssemblyContext() {
        if (this.filter != null){
            this.filter.destroy();
            this.filter = null;
        }
        super.unregisterAssemblyContext();
    }

    /**
     * 执行过滤
     * @param request {@link ServletRequest}, 请求
     * @param response {@link ServletResponse}, 响应
     * @param chain {@link FilterChain}, Filter过滤器
     * @throws IOException
     * @throws ServletException
     */
    public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) 
    		throws IOException, ServletException {
        Filter filter = this.filter;;
        if (filter != null){
            filter.doFilter(request, response, chain);
        }else{
            chain.doFilter(request,response);
        }
    }

    /**
     * 输出服务
     */
    protected synchronized void exportService() {
        if (this.filterRegistration == null) {
            Assembly assembly = this.getAssociatedAssembly();
            ServiceRepository repository = this.getServiceRepository(assembly);
            ServiceRegistry registry = repository.getServiceRegistry();

            Bridge filter = new Bridge();
            Map<String, Object> props =
                    ServicePropertiesBuilder.newInstance()
                            .name(assembly.getConfig().getParameter("filter.name"))
                            .build();

            this.filterRegistration =
                    registry.register(Filter.class, filter , props);
        }
    }

    /**
     * 撤销服务输出
     */
    protected synchronized void unexportService() {
        if (this.filterRegistration != null){
            this.filterRegistration.unregister();
            this.filterRegistration = null;
        }
    }

    /**
     * 桥接器，桥接外部Filter和内部Filter
     */
    private class Bridge implements Filter {

        /**
         * Called by the web container to indicate to a filter that it is
         * being placed into service.
         *
         * <p>The servlet container calls the init
         * method exactly once after instantiating the filter. The init
         * method must complete successfully before the filter is asked to do any
         * filtering work.
         *
         * <p>The web container cannot place the filter into service if the init
         * method either
         * <ol>
         * <li>Throws a ServletException
         * <li>Does not return within a time period defined by the web container
         * </ol>
         *
         * @param filterConfig
         */
        @Override
        public void init(FilterConfig filterConfig) throws ServletException {
            GuiceFilterHandler.this.config = config;
            try {
                GuiceFilterHandler.this.getLifecycleManager().start();
            } catch (Exception e) {
                logError(e.getMessage(), e);
                throw new ServletException(e.getMessage(), e);
            }
        }

        /**
         * The <code>doFilter</code> method of the Filter is called by the
         * container each time a request/response pair is passed through the
         * chain due to a client request for a resource at the end of the chain.
         * The FilterChain passed in to this method allows the Filter to pass
         * on the request and response to the next entity in the chain.
         *
         * <p>A typical implementation of this method would follow the following
         * pattern:
         * <ol>
         * <li>Examine the request
         * <li>Optionally wrap the request object with a custom implementation to
         * filter content or headers for input filtering
         * <li>Optionally wrap the response object with a custom implementation to
         * filter content or headers for output filtering
         * <li>
         * <ul>
         * <li><strong>Either</strong> invoke the next entity in the chain
         * using the FilterChain object
         * (<code>chain.doFilter()</code>),
         * <li><strong>or</strong> not pass on the request/response pair to
         * the next entity in the filter chain to
         * block the request processing
         * </ul>
         * <li>Directly set headers on the response after invocation of the
         * next entity in the filter chain.
         * </ol>
         *
         * @param request {@link ServletRequest}, 请求
         * @param response {@link ServletResponse}, 响应
         * @param chain {@link FilterChain}, Filter过滤链
         */
        @Override
        public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException {
            GuiceFilterHandler.this.doFilter(request, response, chain);
        }

        /**
         * Called by the web container to indicate to a filter that it is being
         * taken out of service.
         *
         * <p>This method is only called once all threads within the filter's
         * doFilter method have exited or after a timeout period has passed.
         * After the web container calls this method, it will not call the
         * doFilter method again on this instance of the filter.
         *
         * <p>This method gives the filter an opportunity to clean up any
         * resources that are being held (for example, memory, file handles,
         * threads) and make sure that any persistent state is synchronized
         * with the filter's current state in memory.
         */
        @Override
        public void destroy() {
            try {
                GuiceFilterHandler.this.getLifecycleManager().stop();
            } catch (Exception e) {
                logError(e.getMessage(), e);
            }
        }
    }
}
